package com.epoint.nio.socket2.read;

import com.epoint.nio.NioUtil;

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

public class Server {
    public static void main(String[] args) throws Exception {
        try(
            // 2.创建服务器
            ServerSocketChannel server = ServerSocketChannel.open()
        ){
            // 2.1绑定监听端口
            server.bind(new InetSocketAddress(8080));
            // 2.2设置非阻塞
            server.configureBlocking(false);

            // 创建Select
            Selector selector = Selector.open();

            /**
             * 注册，绑定两者的关系
             * 监听 server的accept事件
             *
             * 一共可以监听4种事件：
             *      - connect - 客户端连接成功时触发
             *      - accept - 服务器端成功接受连接时触发
             *      - read - 数据可读入时触发，有因为接收能力弱，数据暂不能读入的情况
             *      - write - 数据可写出时触发，有因为发送能力弱，数据暂不能写出的情况
             */
            server.register(selector, SelectionKey.OP_ACCEPT);

            /**
             * 4.一直循环接受客户端
             *
             */
            while (true){
                /**
                 * 重点：判断是否有新事件发生，如果有，则向下执行，如果没有则阻塞
                 *      为什么不直接用server.accept()？他不是和这个方法一样吗，有-》阻塞，无-》放行
                 *
                 *      最最最最重要的是：accept他只能针对他自己这个动作，
                 *      而selector可以针对所有的动作，你细品，你想想如果在不开启多线程（阻塞）的情况下，
                 *      一个客户端发了一次消息之后，想要再发一次，他会卡着，为啥？他会卡在server.accept()这行代码，执行不下去了
                 */
                selector.select();

                // 获取所有事件
                Set<SelectionKey> selectionKeys = selector.selectedKeys();

                // 因为下面会涉及到循环里进行操作，故这里直接获取迭代器
                Iterator<SelectionKey> iterator = selectionKeys.iterator();

                while (iterator.hasNext()){
                    // 事件
                    SelectionKey key = iterator.next();

                    // 根据事件类型进行判断
                    if(key.isAcceptable()){
                        // 获取对应的通道
                        ServerSocketChannel channel = (ServerSocketChannel) key.channel();
                        // 客户端
                        SocketChannel accept = channel.accept();
                        if(accept != null){
                            System.out.println("有一个客户端连接" + accept);
                            // 设置非阻塞
                            accept.configureBlocking(false);

                            // 为该客户端绑定读事件
                            accept.register(selector, SelectionKey.OP_READ);
                        }
                    }else if(key.isReadable()){
                        try{
                            SocketChannel channel = (SocketChannel) key.channel();
                            // 1.开辟一个缓存buffer
                            ByteBuffer buffer = ByteBuffer.allocate(16);

                            // 如果这时候输入20个byte的数据会怎么样？会触发2次事件，直到把通道的数据走完才行
                            if(channel.read(buffer) > 0){
                                buffer.flip();

                                // 打印数据
                                System.out.println(NioUtil.getStr(buffer));

                                buffer.clear();
                            }

                            // 如果这时候输入20个byte的数据会怎么样？只会触发1次事件，因为下面我使用了循环取数据
//                            while (channel.read(buffer) > 0){
//                                // 切到 读模式
//                                buffer.flip();
//
//                                // 这里其实可以直接写出去，这里演示打印出来
//                                System.out.println(NioUtil.getStr(buffer));
//
//                                // 切到 写模式
//                                buffer.clear();
//
//                            }

                        }catch (Exception e){
                            e.printStackTrace();
                            key.cancel();
                        }
                    }

                    /**
                     * 最后记得删除一下，因为selectionKeys.iterator()会把所有的事件放到一个另外一个容器里
                     * 但是有时候 事件已经被消费过了，不能再次消费，故一般用过了就删掉
                     */
                    iterator.remove();
                }
            }
        }catch (Exception e) {
            e.printStackTrace();
        }

    }
}
